using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Xsl;
using System.Xml.XPath;
using System.Resources;
using System.Reflection;
using System.Xml;
using System.IO;
using System.Security.AccessControl;
using System.Net;
using System.Linq;
using DocumentFormat.OpenXml.Wordprocessing;
using DocumentFormat.OpenXml.Packaging;
using System.Globalization;
using DocumentFormat.OpenXml;
using System.Threading;
using System.Drawing;
using System.Drawing.Imaging;
using DocumentFormat.OpenXml.CustomProperties;

namespace fleXdoc.Api
{
    public class DocBuilder : IDisposable
    {
        #region Private instance fields
        private MemoryStream _template;
        private object _templateLock = new object();
        private bool _disposed = false;
        #endregion

        #region Constructor(s) and destructor
        /// <summary>
        /// Creates an instance of DocBuilder.
        /// </summary>
        /// <param name="template">The template to use.\nIt's read into memory to make sure no files keep locks. The position of the stream must be correct: it is not seeked to the begin of the stream.</param>
        public DocBuilder(Stream template)
            : base()
        {
            _template = IOHelper.LoadIntoMemoryStream(template);
        }

        public DocBuilder(string path)
            : base()
        {
            using (Stream templateStream = IOHelper.GetUri(path, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                _template = IOHelper.LoadIntoMemoryStream(templateStream);
            }
        }

        ~DocBuilder()
        {
            Dispose(false);
        }
        #endregion

        #region Public events
        public event ResolveResourceUriEventHandler ResourceUriResolve;        
        #endregion

        #region Public instance methods
        /// <summary>
        /// Builds the document
        /// </summary>
        /// <param name="data">Name of the file containing the data-XML</param>
        /// <param name="nsPrefix">Namespace-prefix that may be used in the template for querying the data-XML</param>
        /// <param name="ns">Namespace-uri which nsPrefix must be mapped to</param>
        /// <param name="output">Stream to write the document to</param>
        public void Build(string data, string nsPrefix, string ns, Stream output, CultureInfo renderCulture, bool validatePackage)
        {
            if (String.IsNullOrEmpty(data))
            {
                throw new ArgumentNullException("data");
            }

            Stream dataStream = null;
            try
            {
                dataStream = IOHelper.GetUri(data, FileMode.Open, FileAccess.Read, FileShare.Read);
            }
            catch (Exception ex)
            {
                throw new ArgumentException("data-file cannot be read (details: " + ex.Message + ")", "data", ex);
            }

            try
            {
                Build(dataStream, nsPrefix, ns, output, renderCulture, validatePackage);
            }
            finally
            {
                dataStream.Close();
                dataStream.Dispose();
            }
        }

        /// <summary>
        /// Builds the document
        /// </summary>
        /// <param name="data">Name of the file containing the data-XML</param>
        /// <param name="nsPrefix">Namespace-prefix that may be used in the template for querying the data-XML</param>
        /// <param name="ns">Namespace-uri which nsPrefix must be mapped to</param>
        /// <param name="output">Name of the file to write the document to</param>
        public void Build(string data, string nsPrefix, string ns, string output, CultureInfo renderCulture, bool validatePackage)
        {
            if (String.IsNullOrEmpty(data))
            {
                throw new ArgumentNullException("data");
            }
            if (String.IsNullOrEmpty(output))
            {
                throw new ArgumentNullException("output");
            }

            Stream dataStream = null;
            try
            {
                dataStream = IOHelper.GetUri(data, FileMode.Open, FileAccess.Read, FileShare.Read);
            }
            catch (Exception ex)
            {
                throw new ArgumentException("data-file cannot be read (details: " + ex.Message + ")", "data", ex);
            }

            Stream outputStream = null;
            try
            {
                // Output-file, so IOHelper.GetURI has no use
                outputStream = File.Open(output, FileMode.Create, FileAccess.Write, FileShare.None);
            }
            catch (Exception ex)
            {
                // dataStream was already opened, so close it now
                dataStream.Close();
                dataStream.Dispose();
                throw new ArgumentException("output-file cannot be created (details: " + ex.Message + ")", "output", ex);
            }

            try
            {
                Build(dataStream, nsPrefix, ns, outputStream, renderCulture, validatePackage);
                outputStream.Close();
                outputStream.Dispose();
                dataStream.Close();
                dataStream.Dispose();
            }
            catch (Exception)
            {
                // Make sure dataStream is closed
                dataStream.Close();
                dataStream.Dispose();

                // Make sure outputStream is closed and deleted
                outputStream.Close();
                outputStream.Dispose();
                if (File.Exists(output))
                {
                    try
                    {
                        File.Delete(output);
                    }
                    catch (Exception) { } // Swallow
                }

                throw; // Rethrow the exception
            }
        }

        /// <summary>
        /// Builds the document
        /// </summary>
        /// <param name="data">Stream containing the data-XML</param>
        /// <param name="nsPrefix">Namespace-prefix that may be used in the template for querying the data-XML</param>
        /// <param name="ns">Namespace-uri which nsPrefix must be mapped to</param>
        /// <param name="output">Name of the file to write the document to</param>
        public void Build(Stream data, string nsPrefix, string ns, string output, CultureInfo renderCulture, bool validatePackage)
        {
            if (String.IsNullOrEmpty(output))
            {
                throw new ArgumentNullException("output");
            }

            Stream outputStream = null;
            try
            {
                // Output-file, so IOHelper.GetURI is not used
                outputStream = File.Open(output, FileMode.Create, FileAccess.Write, FileShare.None);
            }
            catch (Exception ex)
            {
                throw new ArgumentException("output-file cannot be created (details: " + ex.Message + ")", "output", ex);
            }

            try
            {
                Build(data, nsPrefix, ns, outputStream, renderCulture, validatePackage);
                outputStream.Close();
                outputStream.Dispose();
            }
            catch (Exception)
            {
                // Make sure outputStream is closed and deleted
                outputStream.Close();
                outputStream.Dispose();
                if (File.Exists(output))
                {
                    try
                    {
                        File.Delete(output);
                    }
                    catch (Exception) { } // Swallow
                }

                throw; // Rethrow the exception
            }
        }

        /// <summary>
        /// Builds the document
        /// </summary>
        /// <param name="data">Stream containing the data-XML</param>
        /// <param name="nsPrefix">Namespace-prefix that may be used in the template for querying the data-XML</param>
        /// <param name="ns">Namespace-uri which nsPrefix must be mapped to</param>
        /// <param name="output">Stream to write the document to</param>
        public void Build(Stream data, string nsPrefix, string ns, Stream output, CultureInfo renderCulture, bool validatePackage)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }
            if (!data.CanRead)
            {
                throw new ArgumentException("data-stream cannot be read (write only?)", "data");
            }

            // Read the data-stream into an XPathNavigator
            XPathNavigator dataNavigator = null;
            try
            {
                NameTable nt = new NameTable();
                fleXdocXsltContext ctx = new fleXdocXsltContext(nt);
                XmlReaderSettings xrs = new XmlReaderSettings() { NameTable = nt };
                XmlReader xr = XmlReader.Create(data, xrs);
                dataNavigator = new XPathDocument(xr).CreateNavigator();
            }
            catch (Exception ex)
            {
                throw new ArgumentException("data-stream does not contain valid XML (details: " + ex.Message + ")", "data", ex);
            }

            Build(dataNavigator, nsPrefix, ns, output, renderCulture, validatePackage);
        }

        /// <summary>
        /// Builds the document
        /// </summary>
        /// <param name="dataNavigator">XPathNavigator which can be used to browse the data-XML</param>
        /// <param name="nsPrefix">Namespace-prefix that may be used in the template for querying the data-XML</param>
        /// <param name="ns">Namespace-uri which nsPrefix must be mapped to</param>
        /// <param name="output">Stream to write the document to</param>
        private void Build(XPathNavigator dataNavigator, string nsPrefix, string ns, Stream output, CultureInfo renderCulture, bool validatePackage)
        {
            if (dataNavigator == null)
            {
                throw new ArgumentNullException("dataNavigator");
            }
            if (output == null)
            {
                throw new ArgumentNullException("output");
            }
            if (!output.CanWrite)
            {
                throw new ArgumentException("output-stream is read only", "output");
            }
            if (renderCulture == null)
            {
                throw new ArgumentNullException("renderCulture");
            }
            
            // Create an in-memory working-copy of the template
            MemoryStream docIn = null;
            lock (_templateLock)
            {
                docIn = IOHelper.LoadIntoMemoryStream(_template);
            }

            using (docIn)
            {

                // Process the working-copy using the Open XML SDK
                BuildOptions options = new BuildOptions();
                options.Data = dataNavigator;
                options.NamespacePrefix = nsPrefix;
                options.NamespaceUri = ns;
                options.RenderCulture = renderCulture;
                options.ValidatePackage = validatePackage;

                WordprocessingDocument doc = WordprocessingDocument.Open(docIn, true);
                using (fleXdocTemplateProcessor processor = new fleXdocTemplateProcessor())
                {
                    if (ResourceUriResolve != null) // If our event is subscribed, then we also need to subscribe to fleXdoc's event
                    {
                        processor.ResourceUriResolve += delegate(object sender, ResolveResourceUriEventArgs args)
                        {
                            return ResourceUriResolve(sender, args); // Just pass through
                        };
                    }
                    processor.ProcessWordprocessingDocument(doc, options);
                }
                doc.Close(); // Write all changes back to the underlying stream
                docIn.Seek(0, SeekOrigin.Begin);

                // Write the results to the output-stream
                IOHelper.CopyStream(docIn, output);
            }
        }
        #endregion

        #region IDisposable Members
        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    GC.SuppressFinalize(this);

                    // Dispose managed resources
                    _template.Close();
                    _template.Dispose();
                    _template = null;
                }
                _disposed = true;
            }
        }

        void IDisposable.Dispose()
        {
            Dispose(true);
        }

        #endregion
    }
}
